### 1. Объявите класс AbstractClass, объекты которого нельзя было бы создавать. При выполнении команды:

> obj = AbstractClass()

переменная obj должна ссылаться на строку с содержимым:

`"Ошибка: нельзя создавать объекты абстрактного класса"`

<details>
<summary>Подсказка</summary>

```python
class AbstractClass:

    def __new__(cls, *args, **kwargs):
        return  "Ошибка: нельзя создавать объекты абстрактного класса"
       

obj = AbstractClass()

```
</details>

### 2. Объявите класс SingletonFive, с помощью которого можно было бы создавать объекты командой:

> a = SingletonFive(<наименование>)

Здесь <наименование> - это данные, которые сохраняются в локальном свойстве name созданного объекта.

Этот класс должен формировать только первые пять объектов. Остальные (шестой, седьмой и т.д.) должны быть ссылкой на последний (пятый) созданный объект.

Создайте первые десять объектов класса SingletonFive с помощью следующего фрагмента программы:

```python
objs = [SingletonFive(str(n)) for n in range(10)]
```

<details>
<summary>Подсказка</summary>

```python
class SingletonFive:
    __instance = None
    count = 0


    def __new__(cls, *args, **kwargs):
        if cls.count < 5:            
            cls.count += 1
            #cls.__init__
            cls.__instance = super().__new__(cls)
            cls.obj = cls.__instance         
            return  cls.__instance
        else:
            cls.__instance = cls.obj
            #print (cls.count, cls)  
            return  cls.obj 
            
    def __init__(self, name):   
            self.name = name
            #print (self.count, self.name)
            

objs = [SingletonFive(str(n)) for n in range(10)]

# или
class SingletonFive:
    __instances = []
    def __new__(cls, *args, **kwargs):
        if len(cls.__instances) < 5:
            cls.__instances.append(super().__new__(cls))
        return cls.__instances[-1]
    def __init__(self, name):
        self.name = name
objs = [SingletonFive(str(n)) for n in range(10)] 

```

</details>

### 3. В программе объявлена переменная TYPE_OS и два следующих класса:

```python
TYPE_OS = 1 # 1 - Windows; 2 - Linux

class DialogWindows:
    name_class = "DialogWindows"


class DialogLinux:
    name_class = "DialogLinux"

```
Необходимо объявить третий класс с именем `Dialog`, который бы создавал объекты командой:

> dlg = Dialog(<название>)

Здесь <название> - это строка, которая сохраняется в локальном свойстве name объекта `dlg`.

Класс `Dialog` должен создавать объекты класса `DialogWindows`, если переменная `TYPE_OS = 1` и объекты класса `DialogLinux`, если переменная `TYPE_OS` не равна `1`. 

При этом, переменная `TYPE_OS` может меняться в последующих строчках программы. 

Имейте это в виду, при объявлении класса `Dialog`.

<details>
<summary>Подсказка</summary>

```python
TYPE_OS = 1 # 1 - Windows; 2 - Linux

class DialogWindows:
    name_class = "DialogWindows"


class DialogLinux:
    name_class = "DialogLinux"

class Dialog:
     def __new__(cls, *args, **kwargs):
        if TYPE_OS == 1:           
            cls.obj = DialogWindows()
            setattr(cls.obj, 'name', args[0])            
        else:          
            cls.obj = DialogLinux()
            setattr(cls.obj, 'name', args[0]) 
        return cls.obj
    
     def __init__(self, name):        
        self.name = name
        print (self.name)
## или 
class Point:
    
    def __init__(self, x, y):
        self.x = x
        self.y = y
        
        
    def clone(self):
        return Point(self.x, self.y)
    
pt = Point(10, 15)
pt_clone = pt.clone()
```
</details>

### 4. Объявите класс Point для представления точек на плоскости. 

Создавать объекты этого класса предполагается командой:
```
pt = Point(x, y)
```
Здесь `x, y` - числовые координаты точки на плоскости (числа), то есть, в каждом объекте этого класса создаются локальные свойства x, y, которые хранят конкретные координаты точки.

Необходимо в классе `Point` реализовать метод `clone(self)`, который бы создавал новый объект класса `Point` как копию текущего объекта с локальными атрибутами `x, y` и соответствующими значениями.

Создайте в программе объект `pt` класса `Point` и еще один объект `pt_clone` через вызов метода `clone`.


<details>
<summary>Подсказка</summary>

```python

class Point:  
    def __new__(cls, *args, **kwargs):      
        return super().__new__(cls)

    def __init__(self, x, y):
        self.x = x
        self.y = y

    def clone(self):        
        P = Point(self.x, self.y)
        return P
        #print (self.__dict__)


pt = Point(10,20)
pt_clone = Point.clone(pt)

## или 
class Point:
    
    def __init__(self, x, y):
        self.x = x
        self.y = y
        
        
    def clone(self):
        return Point(self.x, self.y)
    
pt = Point(10, 15)
pt_clone = pt.clone()
```
</details>


### 5. Объявите класс с именем Clock и определите в нем следующие переменные и методы:

- приватная локальная переменная time для хранения текущего времени, целое число (своя для каждого объекта класса `Clock` с начальным значением `0`);
- публичный метод `set_time(tm)` для установки текущего времени (присваивает значение tm приватному локальному свойству `time`, если метод `check_time(tm`) возвратил `True`);
- публичный метод `get_time()` для получения текущего времени из приватной локальной переменной `time`;
- приватный метод класса `check_time(tm)` для проверки корректности времени в переменной tm (возвращает `True`, если значение корректно и `False` - в противном случае).

Проверка корректности выполняется по критерию: tm должна быть целым числом, больше или равна нулю и меньше `100 000`.

Объекты класса Clock предполагается использовать командой:

> clock = Clock(время)

Создайте объект clock класса `Clock` и установите время, равным `4530`.

<details>
<summary>Подсказка</summary>

```python
class Clock:
    MIN_TIME = 0
    MAX_TIME = 100_000

    def __init__(self, tm):
        self.__time = tm

    def set_time(self, tm):
        if self.__check_time(tm):
            self.__time = tm

    def get_time(self):
        return self.__time

    def __check_time(self, tm):
        if type(tm) is int and self.MIN_TIME <= tm < self.MAX_TIME:
            return True
        else:
            return False


clock = Clock(4530)
clock.MAX_TIME = 20_000
clock.set_time(30_000)

print(clock.get_time())
```
</details>


### 6.Объявите класс с именем Money и определите в нем следующие переменные и методы:

- приватная локальная переменная money (целочисленная) для хранения количества денег (своя для каждого объекта класса Money);
- публичный метод `set_money(money)` для передачи нового значения приватной локальной переменной `money` (изменение выполняется только если метод `check_money(money)` возвращает значение `True`);
- публичный метод `get_money()` для получения текущего объема средств (денег);
- публичный метод `add_money(mn)` для прибавления средств из объекта mn класса Money к средствам текущего объекта;
- приватный метод класса `check_money(money)` для проверки корректности объема средств в параметре money (возвращает `True`, если значение корректно и `False` - в противном случае).

Проверка корректности выполняется по критерию: параметр money должен быть целым числом, больше или равным нулю.

Пример использования класса Money:
```
mn_1 = Money(10)
mn_2 = Money(20)
mn_1.set_money(100)
mn_2.add_money(mn_1)
m1 = mn_1.get_money()    # 100
m2 = mn_2.get_money()    # 120
```

<details>
<summary>Подсказка</summary>

```python
class Money:

    __money: int

    def __init__(self, money):
        self.__money = money

    def set_money(self, money):
        if self.__check_money(money):
            self.__money = money

    def get_money(self):
        return self.__money

    def add_money(self, mn):         
        self.__money += mn.get_money()

    @classmethod
    def __check_money(cls, money):
        return type(money) in (int,int) and money >= 0
```
</details>


### 7. В программе предполагается реализовать парсер (обработчик) строки с данными string в определенный выходной формат. Для этого объявлен следующий класс:
```python
class Loader:
    @staticmethod
    def parse_format(string, factory):
        seq = factory.build_sequence()
        for sub in string.split(","):
            item = factory.build_number(sub)
            seq.append(item)

        return seq
```

И предполагается его использовать следующим образом:

> res = Loader.parse_format("4, 5, -6", Factory)

На выходе (в переменной res) ожидается получать список из набора целых чисел. Например, для заданной строки, должно получиться:

> [4, 5, -6]

Для реализации этой идеи необходимо вначале программы прописать класс Factory с двумя статическими методами:

`build_sequence()` - для создания пустого списка (метод возвращает пустой список);
`build_number(string)` - для преобразования строки (string) в целое число (метод возвращает полученное целочисленное значение).

Объявите класс с именем `Factory`, чтобы получать на выходе искомый результат.


<details>
<summary>Подсказка</summary>

```python
class Factory:    
  
    @staticmethod    
    def build_sequence():
        N = []
        return N

    @staticmethod
    def build_number(string):                
        return int(string) 

class Loader:
    @staticmethod
    def parse_format(string, factory):
        seq = factory.build_sequence()
        for sub in string.split(","):
            item = factory.build_number(sub)
            seq.append(item)

        return seq


# эти строчки не менять!
res = Loader.parse_format("1, 2, 3, -5, 10", Factory)
```
</details>


## 8. Создайте класс Point, который описывает точку с координатами х и y

В классе необходимо описать:

- конструктор, который принимает в качестве параметров значения для координат x и y
- метод move_to, который принимает в качестве параметров новые значения для координат x и y
- метод move_by, который принимает в качестве параметров новые значения для координат x и y относительно текущих значений
- свойства для изменения и получения значений координат x и y
  Необходимые условия, которые надо учесть:
- при приведении объекта к строке должна возвращаться строка Я - точка: координата_x x координата_y

Как это должно работать

```python
point = Point(10, 20)
print(point) # Я - точка: 10 x 20

point.move_to(100, 200)
print(point.x, ' : ', point.y) # 100 : 200

point.move_by(10, 20)
print(point.x, ' : ', point.y) # 110 : 220

point.x = 30
point.y = 40
print(point) # Я - точка: 30 x 40
```

---
<details>
<summary>Подсказка</summary>

```python
class Point:

    def __init__(self, x, y):
        self.__x = x
        self.__y = y

    def move_to(self, x, y):
        self.__x = x
        self.__y = y

    def move_to(self, x, y):
        self.__x += x
        self.__y += y

    def __repr__(self):
        return f'Я точка: {self.__x}x {self.__y}'

# Продолжение

    def get_x(self):
        return self.__x

    def set_x(self, x):
        self.__x = x

    x = property(get_x, set_x)

# для y через декораторы

    @property
    def y (self):
        return self.__y
    @y.setter
    def y(self, y):
        self.__y = y

# проверяем
p1= Point(1,2)
p1.x = 10
p1.y = 20
print(p1)

Результат:

p1= Point(1,2)

p1.x = 10
p1.y = 20
print(p1)

Ну и до финала, посчитать, сколько у нас создалось таких точек
class Point:
count = 0

    def __init__(self, x, y):
        self.__x = x
        self.__y = y
        Point.count += 1

# Ну и снова инкапсулируем атрибуты
class Point:
# \_\_count = 0

    @classmethod
    def get_count_points(cls):
        return cls.__count

    def __init__(self, x, y):
        self.__x = x
        self.__y = y
        Point.__count += 1
```

</details>

